using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.Extensions.DependencyInjection;
using Silky.Core;
using Silky.Core.Runtime.Rpc;
using Silky.Http.Core.Configuration;
using Silky.Http.Core.Handlers;
using Silky.Rpc.Runtime.Server;
using Silky.Rpc.Security;
using AuthorizeAttribute = Microsoft.AspNetCore.Authorization.AuthorizeAttribute;

namespace Silky.Http.Core.Routing.Builder.Internal;

internal class ServiceEntryDescriptorEndpointFactory
{
    private RequestDelegate CreateRequestDelegate(ServiceEntryDescriptor serviceEntryDescriptor)
    {
        return httpContext =>
        {
            var rpcContextAccessor = httpContext.RequestServices.GetRequiredService<IRpcContextAccessor>();
            RpcContext.Context.RpcServices = httpContext.RequestServices;
            rpcContextAccessor.RpcContext = RpcContext.Context;
            var currentRpcToken = EngineContext.Current.Resolve<ICurrentRpcToken>();
            currentRpcToken.SetRpcToken();
            var messageReceivedHandler = EngineContext.Current.Resolve<IMessageReceivedHandler>();
            return messageReceivedHandler.Handle(serviceEntryDescriptor, httpContext);
        };
    }

    public void AddEndpoints(List<Endpoint> endpoints, HashSet<string> routeNames,
        ServiceEntryDescriptor descriptor, IReadOnlyList<Action<EndpointBuilder>> conventions)
    {
        if (endpoints == null)
        {
            throw new ArgumentNullException(nameof(endpoints));
        }

        if (routeNames == null)
        {
            throw new ArgumentNullException(nameof(routeNames));
        }

        if (descriptor == null)
        {
            throw new ArgumentNullException(nameof(descriptor));
        }

        if (conventions == null)
        {
            throw new ArgumentNullException(nameof(conventions));
        }

        if (descriptor.GovernanceOptions.ProhibitExtranet)
        {
            return;
        }


        var routeKey = $"{descriptor.WebApi}:{descriptor.HttpMethod.ToString().ToUpperInvariant()}";
        if (!routeNames.Add(routeKey))
        {
            return;
        }

        var requestDelegate = CreateRequestDelegate(descriptor);
        var builder =
            new RouteEndpointBuilder(requestDelegate, RoutePatternFactory.Parse(descriptor.WebApi),
                descriptor.RouteOrder)

            {
                DisplayName = descriptor.Id,
            };
        builder.Metadata.Add(descriptor);
        builder.Metadata.Add(descriptor.Id);
        builder.Metadata.Add(new HttpMethodMetadata(new[] { descriptor.HttpMethod.ToString() }));
        AddAuthorizeMetadata(builder, descriptor);
        foreach (var convention in conventions)
        {
            convention(builder);
        }
        endpoints.Add(builder.Build());
    }
    
    private void AddAuthorizeMetadata(RouteEndpointBuilder builder, ServiceEntryDescriptor descriptor)
    {
        if (descriptor.IsAllowAnonymous)
            return;

        if (descriptor.AuthorizeData?.Any() == true)
        {
            foreach (var data in descriptor.AuthorizeData)
            {
                builder.Metadata.Add(new AuthorizeAttribute
                {
                    AuthenticationSchemes = data.AuthenticationSchemes,
                    Policy = data.Policy,
                    Roles = data.Roles
                });
            }
        }
        else if (EngineContext.Current.ApplicationOptions.GlobalAuthorize)
        {
            builder.Metadata.Add(new AuthorizeAttribute());
        }
    }

}